/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package agents;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import jade.proto.SubscriptionInitiator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import model.MuseumArtifacts;

/**
 *
 * @author CUNEYT
 *
 * This class is the Tour Guide agent which is responsible for responding to requests made
 * by the Profiler agents. This agent responds the requests by performing a search over all
 * the museums it knows, collecting the results and sending back the final result to the Profiler.
 *
 */
public class TourGuideAgent extends Agent {

    private ArrayList<AID> museums;

    @Override
    protected void setup() {
        museums = new ArrayList<AID>();
        registerService();
        subscribeForMuseums();
        addBehaviour(new MuseumSearcher(this, 10000));
        addBehaviour(new CyclicBehaviour(this) {

            @Override
            public void action() {
                ACLMessage msg = myAgent.receive(MessageTemplate.MatchPerformative(ACLMessage.CFP));
                if (msg != null) {
                    addBehaviour(new ResultCollector(msg));
                } else {
                    block();
                }
            }
        });
    }

    @Override
    protected void takeDown() {
        try {
            DFService.deregister(this);
        } catch (FIPAException ex) {
            Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /*
     *This method is invoked in the initialization setup of the agent in order to
     * register the services provided by this agent to the Directory Facilitator.
     */
    private void registerService() {
        DFAgentDescription dfad = new DFAgentDescription();
        dfad.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType("tour-guide");
        sd.setName("DAIIA10-HW1-TOUR-GUIDE");
        dfad.addServices(sd);
        try {
            DFService.register(this, dfad);
        } catch (FIPAException ex) {
            Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /*
     * This private class is responsible for searching the museums registered with
     * the DF. It has a TickerBehaviour which means that this action is performed
     * periodically.
     */

    private class MuseumSearcher extends TickerBehaviour {

        public MuseumSearcher(Agent a, long time) {
            super(a, time);
        }

        @Override
        protected void onTick() {
            DFAgentDescription template = new DFAgentDescription();
            ServiceDescription sd = new ServiceDescription();
            sd.setType("museum/gallery");
            template.addServices(sd);
            DFAgentDescription[] result = null;
            try {
                result = DFService.search(myAgent, template);
                for (int i = 0; i < result.length; i++) {

                    if (!museums.contains(result[i].getName())) {
                        museums.add(result[i].getName());
                    }

                }
            } catch (FIPAException ex) {
                Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /*
     * This private class is responsible for sending a lookup message to all museums
     * it knows and collecting the incoming results from all of them. The final result
     * is sent to the profiler agent who made the request.
     */

    private class ResultCollector extends Behaviour {

        private ArrayList<MuseumArtifacts> result;
        private int step = 0;
        private int replyCount = 0;
        private ACLMessage msg = null;
        private ACLMessage reply = null;
        private MessageTemplate mt;

        public ResultCollector(ACLMessage msg) {
            super();
            this.msg = msg;
            this.reply = msg.createReply();
            this.result = new ArrayList<MuseumArtifacts>();
        }

        @Override
        public void action() {

            switch (step) {
                case 0:
                    ACLMessage msgToMCast = new ACLMessage(ACLMessage.CFP);
                    for (Iterator<AID> it = museums.iterator(); it.hasNext();) {
                        AID museum = it.next();
                        msgToMCast.addReceiver(museum);
                    }
                    msgToMCast.setContent(msg.getContent());
                    msgToMCast.setReplyWith("artifact" + System.currentTimeMillis());
                    myAgent.send(msgToMCast);
                    mt = MessageTemplate.MatchReplyWith(msgToMCast.getReplyWith());
                    step = 1;
                    break;
                case 1:
                    ACLMessage rep = myAgent.receive(mt);
                    if (rep != null) {
                        if (rep.getPerformative() == ACLMessage.PROPOSE) {
                            try {
                                ArrayList<String> artifacts = (ArrayList<String>) rep.getContentObject();
                                result.add(new MuseumArtifacts(rep.getSender(), listToString(artifacts)));
                            } catch (UnreadableException ex) {
                                Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                        replyCount++;
                        if (replyCount >= museums.size()) {
                            try {
                                reply.setContentObject(result);
                                reply.setPerformative(ACLMessage.PROPOSE);
                                myAgent.send(reply);
                            } catch (IOException ex) {
                                Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
                            }
                            step = 2;
                        }
                    } else {
                        block();
                    }
                    break;
            }
        }

        @Override
        public boolean done() {
            return step == 2;
        }

        private String listToString(ArrayList<String> list) {
            StringBuilder sb = new StringBuilder();
            for (Iterator<String> it = list.iterator(); it.hasNext();) {
                String string = it.next();
                sb.append(string);
                sb.append(',');
            }
            return sb.toString();
        }
    }

    /*
     * This method is invoked in the initialization of the agent which subscribes
     * this agent to the DF in order to be informed about the new museums.
     */

    private void subscribeForMuseums() {
        DFAgentDescription dfad = new DFAgentDescription();
        dfad.setName(getAID());
        ServiceDescription sd = new ServiceDescription();
        sd.setType("gallery/museum");
        dfad.addServices(sd);
        AID df = getDefaultDF();
        ACLMessage subs = DFService.createSubscriptionMessage(this, df, dfad, null);

        Behaviour b = new SubscriptionInitiator(this, subs) {

            @Override
            protected void handleInform(ACLMessage inform) {
                try {
                    DFAgentDescription[] dfds = DFService.decodeNotification(inform.getContent());
                    for (int i = 0; i < dfds.length; i++) {
                        if (!museums.contains(dfds[i].getName())) {
                            museums.add(dfds[i].getName());
                        }
                    }
                } catch (FIPAException ex) {
                    Logger.getLogger(TourGuideAgent.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        };
        addBehaviour(b);
    }
}
